import { ETTask, ETVoid } from "../../../Support/Share/Lib/Async/ETTask";
import { Queue } from "../../../Support/Share/Lib/Collections/Queue";
import { Dictionary } from "../../../Support/Share/Lib/Dictionary";
import { MultiMap } from "../../../Support/Share/Lib/MultiMap";
import { ImplementsInterface } from "../../../Decorator/Decorator";
import { Singleton } from "../../Singleton/Singleton";
import { EventSystem } from "../EventSystem/EventSystem";
import { Log } from "../Log/Log";
import { TimeHelper } from "../Time/TimeHelper";
import { ETCancellationToken } from "../../../Support/Share/Lib/Async/ETCancellationToken";
import { ISingletonUpdate } from "../../Singleton/ISingletonUpdate";

export enum TimerClass {
    None,
    OnceTimer,
    OnceWaitTimer,
    RepeatedTimer,
}

export class TimerAction {

    public static Create(id: number, timerClass: TimerClass, startTime: number, time: number, type: number, obj: Object): TimerAction {
        let timerAction = new TimerAction();
        timerAction.Id = id;
        timerAction.TimerClass = timerClass;
        timerAction.StartTime = startTime;
        timerAction.Object = obj;
        timerAction.Time = time;
        timerAction.Type = type;
        return timerAction;
    }

    public Id: number;

    public TimerClass: TimerClass;

    public Object: Object;

    public StartTime: number;

    public Time: number;

    public Type: number;

    public Recycle() {
        this.Id = 0;
        this.Object = null;
        this.StartTime = 0;
        this.Time = 0;
        this.TimerClass = TimerClass.None;
        this.Type = 0;
    }
}

export class TimerCallback {
    public Args: Object;
}

@ImplementsInterface(ISingletonUpdate)
export class TimerComponent extends Singleton<TimerComponent> implements ISingletonUpdate {

    private static _Instance: TimerComponent;
    static get Instance() {
        return this._Instance;
    }

    protected set Instance(value: TimerComponent) {
        TimerComponent._Instance = value;
    }

    private readonly TimeId = new MultiMap<number, number>();

    private readonly timeOutTime = new Queue<number>();

    private readonly timeOutTimerIds = new Queue<number>();

    private readonly timerActions = new Dictionary<number, TimerAction>();

    private idGenerator: number = 0;

    // 记录最小时间，不用每次都去MultiMap取第一个值
    private minTime = Number.MAX_VALUE;

    constructor() {
        super();
    }

    private GetId(): number {
        return ++this.idGenerator;
    }

    private static GetNow(): number {
        return TimeHelper.ClientFrameTime();
    }

    Update() {
        if (this.TimeId.Count == 0) {
            return;
        }
        let timeNow = TimerComponent.GetNow();
        if (timeNow < this.minTime) {
            return;
        }
        this.TimeId.forEach((value, k) => {
            if (k > timeNow) {
                this.minTime = k;
                return;
            }
            this.timeOutTime.Enqueue(k);
        });
        while (this.timeOutTime.Count > 0) {
            let time = this.timeOutTime.Dequeue();
            let list: number[] = this.TimeId.TryGetValue(time);
            for (let i = 0; i < list.length; ++i) {
                let timerId = list[i];
                this.timeOutTimerIds.Enqueue(timerId);
            }
            this.TimeId.Remove(time);
        }
        while (this.timeOutTimerIds.Count > 0) {
            let timerId = this.timeOutTimerIds.Dequeue();
            let timerAction = this.timerActions.RemoveValue(timerId);
            if (!timerAction) {
                continue;
            }
            this.Run(timerAction);
        }
    }

    private Run(timerAction: TimerAction): void {
        switch (timerAction.TimerClass) {
            case TimerClass.OnceTimer:
                {
                    let tcb = new TimerCallback();
                    tcb.Args = timerAction.Object;
                    EventSystem.Instance.Invoke(timerAction.Type, tcb);
                    timerAction.Recycle();
                    break;
                }
            case TimerClass.OnceWaitTimer:
                {
                    let tcs = timerAction.Object as ETVoid;
                    tcs.SetResult();
                    timerAction.Recycle();
                    break;
                }
            case TimerClass.RepeatedTimer:
                {
                    let timeNow = TimerComponent.GetNow();
                    timerAction.StartTime = timeNow;
                    this.AddTimer(timerAction);

                    let tcb = new TimerCallback();
                    tcb.Args = timerAction.Object;
                    EventSystem.Instance.Invoke(timerAction.Type, tcb);
                    break;
                }
        }
    }

    private AddTimer(timer: TimerAction): void {
        let tillTime = timer.StartTime + timer.Time;
        this.TimeId.Add(tillTime, timer.Id);
        this.timerActions.Add(timer.Id, timer);
        if (tillTime < this.minTime) {
            this.minTime = tillTime;
        }
    }

    public Remove(id: number): boolean {
        if (id == 0) {
            return false;
        }
        let timerAction = this.timerActions.RemoveValue(id);
        if (!timerAction) {
            return false;
        }
        return true;
    }

    public async WaitTillAsync(tillTime: number, cancellationToken: ETCancellationToken = null): Promise<void> {
        let timeNow = TimerComponent.GetNow();
        if (timeNow >= tillTime) {
            return;
        }
        let tcs = ETTask.Create<void>();
        let timer = TimerAction.Create(this.GetId(), TimerClass.OnceWaitTimer, timeNow, tillTime - timeNow, 0, tcs);
        this.AddTimer(timer);
        let timerId = timer.Id;
        let CancelAction = () => {
            if (this.Remove(timerId)) {
                tcs.SetResult();
            }
        }
        try {
            cancellationToken?.Add(CancelAction);
            await tcs;
        } finally {
            cancellationToken?.Remove(CancelAction);
        }
    }

    public async WaitFrameAsync(cancellationToken: ETCancellationToken = null): Promise<void> {
        await this.WaitAsync(1, cancellationToken);
    }

    public async WaitAsync(time: number, cancellationToken: ETCancellationToken = null): Promise<void> {
        if (time == 0) {
            return;
        }
        let timeNow = TimerComponent.GetNow();
        let tcs = ETTask.Create<void>();
        let timer = TimerAction.Create(this.GetId(), TimerClass.OnceWaitTimer, timeNow, time, 0, tcs);
        this.AddTimer(timer);
        let timerId = timer.Id;
        let CancelAction = () => {
            if (this.Remove(timerId)) {
                tcs.SetResult();
            }
        }
        try {
            cancellationToken?.Add(CancelAction);
            await tcs;
        } finally {
            cancellationToken?.Remove(CancelAction);
        }
    }

    public NewOnceTimer(tillTime: number, type: number, args: Object): number {
        let timeNow = TimerComponent.GetNow();
        if (tillTime < timeNow) {
            Log.Error(`new once time too small: ${tillTime}`);
        }
        let timer = TimerAction.Create(this.GetId(), TimerClass.OnceTimer, timeNow, tillTime - timeNow, type, args);
        this.AddTimer(timer);
        return timer.Id;
    }

    public NewFrameTimer(type: number, args: Object): number {
        return this.NewRepeatedTimerInner(100, type, args);
    }

    private NewRepeatedTimerInner(time: number, type: number, args: Object): number {
        if (time < 100) {
            throw new Error(`repeated timer < 100, timerType: time: ${time}`);
        }
        let timeNow = TimerComponent.GetNow();
        let timer = TimerAction.Create(this.GetId(), TimerClass.RepeatedTimer, timeNow, time, type, args);
        // 每帧执行的不用加到timerId中，防止遍历
        this.AddTimer(timer);
        return timer.Id;
    }

    public NewRepeatedTimer(time: number, type: number, args: Object): number {
        if (time < 100) {
            Log.Error(`time too small: ${time}`);
            return 0;
        }
        return this.NewRepeatedTimerInner(time, type, args);
    }

}